package org.filecollector.filecontroller.basicsfilecontroller;

import lombok.NonNull;
import org.filecollector.filecontroller.operationresults.OperationResponse;
import org.filecollector.filecontroller.virtualcatalogue.VirtualCatalogue;
import org.filecollector.filecontroller.virtualcatalogue.groupname.GroupName;

import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

abstract class BasicsFileControllerStaticMethods {
    /**
     * Create file and fills it with buffer
     * @param id file id
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param warner Logger warn consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param producer get VirtualCatalogue from map by id
     * @param buffer buffer that will be written to new file
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if it needs to call to TroublesHunter</li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - because id for new files is distributing by system and therefore creates by it.
     */
    public static OperationResponse createBuf(Long id, String buffer, Consumer<String> tracer,
                                              Consumer<String> informer, Consumer<String> warner,
                                              Predicate<Long> idChecker, Function<Long, VirtualCatalogue> producer) {
        tracer.accept(
                String.format(
                        "Creating file %s with buffer",
                        Long.toString(id, 16)
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Incorrect id: %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = producer.apply(id);
        if (trackingDir.touch(id).isEmpty()) {
            warner.accept(
                    String.format(
                            "File with id %s was not created, but should be to",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        if (trackingDir.writeDirectly(id, buffer.toCharArray()).isEmpty()) {
            warner.accept(
                    String.format(
                            "Buffer wasn't have written to file with id %s, but should be to",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        tracer.accept(
                String.format(
                        "File %s created and placed buffer to",
                        Long.toString(id, 16)
                )
        );
        return OperationResponse.constructSuccess();
    }

    /**
     * Create empty id
     * @param id file id
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param warner Logger warn consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param producer get VirtualCatalogue from map by id
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if it needs to call to TroublesHunter</li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - because id for new files is distributing by system and therefore creates by it.
     */
    public static OperationResponse createEmpty(Long id, Consumer<String> tracer, Consumer<String> informer,
                                         Consumer<String> warner, Predicate<Long> idChecker,
                                         Function<Long, VirtualCatalogue> producer) {
        tracer.accept(
                String.format(
                        "Creating empty file wit id %s",
                        Long.toString(id, 16)
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Incorrect id: %s - cannot trace file with this id",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = producer.apply(id);
        if (trackingDir.touch(id).isEmpty()) {
            warner.accept(
                    String.format(
                            "File with id %s was not created, but should be to",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        tracer.accept(
                String.format(
                        "File %s created and placed buffer to",
                        Long.toString(id, 16)
                )
        );
        return OperationResponse.constructSuccess();
    }

    /**
     * Create new catalogue with {@code namePath}
     * @param namePath path name of new file
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param creator creates new VirtualCatalogue or Optional.empty()
     * @param canTraceCheck return true if id can be traced by program
     * @param intersectionCheck return true if new catalogue intersects with any tracking catalogue
     * @param mapPutter puts VirtualCatalogue to map
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if it needs to call to TroublesHunter</li>
     *     <li> {@link OperationResponse#constructCatalogueAlreadyTracking()} if catalogue already tracking</li>
     *     <li> {@link OperationResponse#constructWrongCatalogueName()} if catalogue intersects to one of tracking</li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - catalogues controls only by system
     */
    public static OperationResponse createCatalogue(@NonNull String namePath, Consumer<String> tracer,
                                             Consumer<String> informer, Supplier<Optional<VirtualCatalogue>> creator,
                                             Predicate<GroupName> canTraceCheck, Predicate<GroupName> intersectionCheck,
                                             Consumer<VirtualCatalogue> mapPutter) {
        tracer.accept(
                String.format(
                        "Creating catalogue with name %s",
                        namePath
                )
        );
        var add = creator.get();
        if (add.isEmpty()) {
            informer.accept(
                    String.format(
                            "Catalogue %s wasn't created",
                            namePath
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        if (!canTraceCheck.test(add.get().getName())) {
            informer.accept(
                    String.format(
                            "Catalogue %s already tracking. Aborting.",
                            namePath
                    )
            );
            return OperationResponse.constructCatalogueAlreadyTracking();
        }
        if (intersectionCheck.test(add.get().getName())) {
            informer.accept(
                    String.format(
                            "Catalogue %s interferer with tracking catalogues. Aborting.",
                            namePath
                    )
            );
            return OperationResponse.constructWrongCatalogueName();
        }
        mapPutter.accept(add.get());
        tracer.accept(
                String.format(
                        "Catalogue %s created.",
                        namePath
                )
        );
        return OperationResponse.constructSuccess();
    }

    /**
     * Read file with id of size
     * @param id id of ile
     * @param size size of returning buffer
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getter return VirtualCatalogue from map
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccessBuffered(char[])} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if it needs to call to TroublesHunter</li>
     *     <li> {@link OperationResponse#constructExceptionThrew()}  if exception thrown and unhatched</li>
     *     </uL>
     * @apiNote <u>USER</u>
     */
    public static OperationResponse readBuf(Long id, int size, Consumer<String> tracer, Consumer<String> informer,
                                     Predicate<Long> idChecker, Function<Long, VirtualCatalogue> getter) {
        tracer.accept(
                String.format(
                        "Reading buffer of size %d file with id %s.",
                        size ,
                        Long.toString(id, 16)
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Id %s of file doesn't match to tracking diapasons. Aborting.",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = getter.apply(id);
        if (!trackingDir.checkId(id)) {
            informer.accept(
                    String.format(
                            "File with id %s doesn't exist. Aborting.",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructNotSuchId();
        }
        var ret = trackingDir.readDirectly(id, size);
        if (ret.isEmpty()) {
            informer.accept(
                    String.format(
                            "Something went wrong. File with id %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructExceptionThrew();
        }
        tracer.accept(
                String.format(
                        "Get buffer of size %d from file with id %s",
                        ret.get().length,
                        Long.toString(id, 16)
                )
        );
        return OperationResponse.constructSuccessBuffered(ret.get());
    }

    /**
     * Return all ids of catalogue with id
     * @param id catalogue id
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getter return VirtualCatalogue from map
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccessBuffered(char[])} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructNotSuchId()} if this ID doesn't exist</li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - only system has access to catalogues
     */
    public static OperationResponse readIdGroup(@NonNull Long id, Consumer<String> tracer, Consumer<String> informer,
                                         Predicate<Long> idChecker, Function<Long, VirtualCatalogue> getter) {
        tracer.accept(
                String.format(
                        "Getting all id's of tracking group contains id %s",
                        Long.toString(id, 16)
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Groups contain id %s doesn't exist",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = getter.apply(id);
        if (!trackingDir.checkId(id)) {
            informer.accept(
                    String.format(
                            "Group can't contain id %s, but it should to",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        var ret = "{\"id\":[" + Arrays.stream(trackingDir.listIds())
                .map(
                        a -> Long.toString(a, 16)
                )
                .sorted()
                .collect(
                        Collectors.joining(", ")
                ) + "]}";
        tracer.accept(
                String.format(
                        "Get all id's of tracking group contains id %s",
                        Long.toString(id, 16)
                )
        );
        return OperationResponse.constructSuccessBuffered(ret.toCharArray());
    }

    /**
     * @param tracer Logger trace consumer
     * @param streamGetter get Stream of GroupNames
     * Get all tracking catalogues
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccessBuffered(char[])} if everything is OK </li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - only system has access to catalogues
     */
    public static OperationResponse readGroups(Consumer<String> tracer, Supplier<Stream<GroupName>> streamGetter) {
        tracer.accept(
                "Reading groups"
        );
        var ret = "{\"groups\":[" + streamGetter.get()
                .sorted()
                .map(GroupName::toJSONElement)
                .collect(
                        Collectors.joining(", ")
                ) + "]}";
        tracer.accept(
                "Groups read"
        );
        return OperationResponse.constructSuccessBuffered(ret.toCharArray());
    }

    /**
     * list all tracking ids
     * @param tracer Logger trace consumer
     * @param streamGetter get Stream of VirtualCatalogues
     * @return <uL>
     *        <li> {@link OperationResponse#constructSuccessBuffered(char[])} if everything is OK </li>
     *        </uL>
     * @apiNote <u>ADMIN</u> - only system can get access to list of all id
     */
    public static OperationResponse readAllIds(Consumer<String> tracer, Supplier<Stream<VirtualCatalogue>> streamGetter) {
        tracer.accept(
                "Getting all tracking id's"
        );
        var ret = "{\"id\":[" + streamGetter.get()
                .map(VirtualCatalogue::listIds)
                .map(a -> Arrays.stream(a)
                        .map(b -> Long.toString(b, 16))
                        .sorted()
                        .collect(Collectors.joining(", "))
                ).collect(Collectors.joining(", ")) + "]}";
        tracer.accept(
                "All tracking id's have gotten"
        );
        return OperationResponse.constructSuccessBuffered(ret.toCharArray());
    }

    /**
     * List all free ids by group Id
     * @param groupId id of catalogue
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param arraySupplier return bi array of free diapasons
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccessBuffered(char[])} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - only system can get access to list of all id
     */
    public static OperationResponse readFreeIds(@NonNull Long groupId, Consumer<String> tracer,
                                         Consumer<String> informer, Predicate<Long> idChecker,
                                         Function<Long, Long[][]> arraySupplier
    ) {
        tracer.accept(
                String.format(
                        "Get all free id of group contains id %s",
                        Long.toString(groupId, 16)
                )
        );
        if (!idChecker.test(groupId)) {
            informer.accept(
                    String.format(
                            "Group that may contain id %s doesn't exist",
                            Long.toString(groupId, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        StringBuilder ret = new StringBuilder("{\"diapason\":[");
        var get = arraySupplier.apply(groupId);
        for (var element : get) {
            ret.append(
                    String.format(
                            "[%s,%s],",
                            Long.toHexString(element[0]),
                            Long.toHexString(element[1])
                    )
            );
        }
        ret.deleteCharAt(ret.lastIndexOf(","));
        ret.append("]}");
        tracer.accept(
                String.format(
                        "Free id of group contains id %s gotten",
                        Long.toString(groupId, 16)
                )
        );
        return OperationResponse.constructSuccessBuffered(ret.toString().toCharArray());
    }

    /**
     * Update file with id and renew it with buffer
     * @param id file
     * @param buffer new content of file
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getterVC return virtual catalogue traces id
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructNotSuchId()} if ID doesn't exist</li>
     *     <li> {@link OperationResponse#constructExceptionThrew()} exception threw and uncouth</li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if something went wrong</li>
     *     </uL>
     * @apiNote <u>USER</u>
     */
    public static OperationResponse updateBuf(Long id, char[] buffer, Consumer<String> tracer, Consumer<String> informer,
                                       Predicate<Long> idChecker, Function<Long, VirtualCatalogue> getterVC) {
        tracer.accept(
                String.format(
                        "Updating of file with id %s with buffer size of %d",
                        Long.toString(id, 16),
                        buffer.length
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Group that may contain id %s doesn't exist",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = getterVC.apply(id);
        if (!trackingDir.checkId(id)) {
            informer.accept(
                    String.format(
                            "No such file with id %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructNotSuchId();
        }
        var ret = trackingDir.writeDirectly(id, buffer);
        if (ret.isEmpty()) {
            informer.accept(
                    String.format(
                            "Something went wrong while writing to file with id %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructExceptionThrew();
        }
        if (ret.getAsLong() != id) {
            informer.accept(
                    String.format(
                            "Something went wrong while writing to file with id %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        tracer.accept(
                String.format(
                        "File with id %s updated with buffer size of %d",
                        Long.toString(id, 16),
                        buffer.length
                )
        );
        return OperationResponse.constructSuccess();
    }

    /**
     * Update file with id and append it with buffer
     * @param id file
     * @param buffer new content of file
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getterVC return virtual catalogue traces id
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructNotSuchId()} if ID doesn't exist</li>
     *     <li> {@link OperationResponse#constructExceptionThrew()} exception threw and uncouth</li>
     *     <li> {@link OperationResponse#constructSomethingGoesWrong()} if something went wrong</li>
     *     </uL>
     * @apiNote <u>USER</u>
     */
    public static OperationResponse updateAppendBuf(@NonNull Long id, @NonNull char[] buffer,
                                             Consumer<String> tracer, Consumer<String> informer,
                                             Predicate<Long> idChecker,
                                             Function<Long, VirtualCatalogue> getterVC) {
        tracer.accept(
                String.format(
                        "File with id %s is updating with buffer size of %d",
                        Long.toString(id, 16),
                        buffer.length
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Catalogue doesn't trace %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = getterVC.apply(id);
        if (!trackingDir.checkId(id)) {
            informer.accept(
                    String.format(
                            "Catalogue doesn't trace %s",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructNotSuchId();
        }
        var ret = trackingDir.addDirectly(id, buffer);
        if (ret.isEmpty()) {
            informer.accept(
                    String.format(
                            "File with id %s wasn't updated",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructExceptionThrew();
        }
        if (ret.getAsLong() != id) {
            informer.accept(
                    String.format(
                            "In file %s something went wrong while adding",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        tracer.accept(
                String.format(
                        "File with id %s updated with buffer size of %d",
                        Long.toString(id, 16),
                        buffer.length
                )
        );
        return OperationResponse.constructSuccess();
    }

    /**
     * Delete file by id
     * @param id id of deleting file
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getterVC return virtual catalogue traces id
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     <li> {@link OperationResponse#constructWrongId()} if incorrect ID </li>
     *     <li> {@link OperationResponse#constructNotSuchId()} if ID doesn't exist</li>
     *     <li> {@link OperationResponse#constructFileAlreadyRemoved()} if file removed</li>
     *     </uL>
     * @apiNote <u>USER</u>
     */
    public static OperationResponse delete(Long id, Consumer<String> tracer, Consumer<String> informer,
                                    Predicate<Long> idChecker, Function<Long, VirtualCatalogue> getterVC) {
        tracer.accept(
                String.format(
                        "Started deleting of file with id %s",
                        Long.toString(id, 16)
                )
        );
        if (!idChecker.test(id)) {
            informer.accept(
                    String.format(
                            "Id %s doesn't be traced by Program",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var trackingDir = getterVC.apply(id);
        if (trackingDir == null) {
            informer.accept(
                    String.format(
                            "Tracking directory %s is null some why",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        if (!trackingDir.checkId(id)) {
            informer.accept(
                    String.format(
                            "File with id %s doesn't even exist",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructNotSuchId();
        }
        var ret = trackingDir.remove(id);
        if (ret) {
            informer.accept(
                    String.format(
                            "In file %s something went wrong while removing",
                            Long.toString(id, 16)
                    )
            );
            return OperationResponse.constructSuccess();
        }
        tracer.accept(
                String.format(
                        "File with id %s removed",
                        Long.toString(id, 16)
                )
        );
        return OperationResponse.constructFileAlreadyRemoved();
    }

    /**
     * Delete catalogue by id due to removing catalogue by id from CatalogueMap
     * @param anyId id of deleting catalogue
     * @param tracer Logger trace consumer
     * @param remover removes catalogue by id
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - only system has access to catalogues
     */
    public static OperationResponse deleteCatalogueSoft(Long anyId, Consumer<String> tracer, Consumer<Long> remover) {
        tracer.accept(
                String.format(
                        "Directory tracing id %s removed",
                        Long.toString(anyId, 16)
                )
        );
        remover.accept(anyId);
        return OperationResponse.constructSuccess();
    }

    /**
     * Deeply removing of tracking catalogue by id
     * @param anyId id of deleting catalogue
     * @param tracer Logger trace consumer
     * @param informer Logger info consumer
     * @param idChecker checks if program trace this id. True if it can, false in other cases
     * @param getterVC return virtual catalogue traces id
     * @param catalogueRemover remove catalogue from map <b>after</b> using of getterVC
     * @return <uL>
     *     <li> {@link OperationResponse#constructSuccess()} if everything is OK </li>
     *     </uL>
     * @apiNote <u>ADMIN</u> - only system has access to catalogues
     */
    public static OperationResponse deleteCatalogueHard(@NonNull Long anyId, Consumer<String> tracer,
                                                        Consumer<String> informer, Predicate<Long> idChecker,
                                                        Function<Long, VirtualCatalogue> getterVC,
                                                        Consumer<Long> catalogueRemover) {
        tracer.accept(
                String.format(
                        "Removing catalogue that tracing %s",
                        Long.toString(anyId, 16)
                )
        );
        if (!idChecker.test(anyId)) {
            informer.accept(
                    String.format(
                            "Catalogue that tracking file with id %s doesn't exist",
                            Long.toString(anyId, 16)
                    )
            );
            return OperationResponse.constructWrongId();
        }
        var remCatalogue = getterVC.apply(anyId);
        catalogueRemover.accept(anyId);
        var ret = remCatalogue.removeCatalogue();
        if (!ret) {
            informer.accept(
                    String.format(
                            "Catalogue with id %s wasn't removed",
                            Long.toString(anyId, 16)
                    )
            );
            return OperationResponse.constructSomethingGoesWrong();
        }
        tracer.accept(
                String.format(
                        "Catalogue that traces id %s removed",
                        Long.toString(anyId, 16)
                )
        );
        return OperationResponse.constructSuccess();
    }
}

